﻿using Amazon.S3;
using Amazon.S3.Model;
using Amazon.S3.Transfer;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace Storage.Aliyun
{
    public class StorageAws
    {
        private IAmazonS3 _client { get; set; }

        /// <summary>
        /// 进度
        /// </summary>
        public Action<int> Progress => null;

        /// <summary>
        /// 每片大小
        /// </summary>
        public int PartSize => 10 * 1024 * 1024;

        /// <summary>
        /// 最大分片数量
        /// </summary>
        public int MaxPart => 9999;


        public StorageAws()
        {
            _client = new AmazonS3Client("ak", "sk", new AmazonS3Config { ServiceURL = "endpoint", ForcePathStyle = true, UseHttp = true, RegionEndpoint = Amazon.RegionEndpoint.CNNorth1 });
        }

        /// <summary>
        /// 根据位置大小获取文件流
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public byte[] GetStreamData(string bucketName, string objectName, long offset, long count)
        {
            if (_client == null)
                return null;
            GetObjectRequest request = new GetObjectRequest
            {
                BucketName = bucketName,
                Key = objectName,
                ByteRange = new ByteRange(offset, offset + count - 1)
            };
            try
            {
                int len = 0;
                byte[] data = new byte[count];

                using (GetObjectResponse response = _client.GetObjectAsync(request).GetAwaiter().GetResult())
                using (Stream responseStream = response.ResponseStream)
                {
                    while (true)
                    {
                        try
                        {
                            len += responseStream.Read(data, len, data.Length - len);
                            if (len == data.Length)
                                break;
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e);
                            return null;
                        }
                    }
                }
                return data;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                return null;
            }
        }

        /// <summary>
        /// 获取文件大小
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <returns></returns>
        public Int64 GetContentLength(string bucketName, string objectName)
        {
            if (_client == null)
                return -1;
            try
            {
                GetObjectResponse response = _client.GetObjectAsync(bucketName, objectName).GetAwaiter().GetResult();
                return response?.ContentLength ?? -1;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                return -1;
            }
        }

        /// <summary>
        /// 文件是否存在
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <returns></returns>
        public bool DoesObjectExist(string bucketName, string objectName)
        {
            if (_client == null)
                return false;
            try
            {
                return _client.DoesS3BucketExistAsync(objectName).GetAwaiter().GetResult();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                return false;
            }
        }

        /// <summary>
        /// 下载文件
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <returns></returns>
        public byte[] DownLoadObject(string bucketName, string objectName)
        {
            if (_client == null)
                return null;
            try
            {
                Int64 len = GetContentLength(bucketName, objectName);
                return GetStreamData(bucketName, objectName, 0, len);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// 分块下载（暂未断点续传）
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <param name="localFileName"></param>
        /// <param name="checkPointDir"></param>
        /// <returns></returns>
        public void ResumDownLoadObject(string bucketName, string objectName, string localFileName, string checkPointDir)
        {
            if (_client == null)
                throw new ArgumentNullException(nameof(_client));
            try
            {
                TransferUtility transfer = new TransferUtility(this._client);
                TransferUtilityDownloadRequest transferUtilityDownloadRequest = new TransferUtilityDownloadRequest
                {
                    BucketName = bucketName,
                    Key = objectName,
                    FilePath = localFileName
                };
                if (Progress != null)
                    transferUtilityDownloadRequest.WriteObjectProgressEvent += (sender, arg) =>
                    {
                        Progress?.Invoke(arg.PercentDone);
                    };
                transfer.DownloadAsync(transferUtilityDownloadRequest).GetAwaiter().GetResult();
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        /// <summary>
        /// 上传文件
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <param name="localFileName"></param>
        /// <returns></returns>
        public bool UploadObject(string bucketName, string objectName, string localFileName)
        {
            if (_client == null | !File.Exists(localFileName))
                return false;
            try
            {
                PutObjectRequest request = new PutObjectRequest
                {
                    BucketName = bucketName,
                    Key = objectName,
                    FilePath = localFileName
                };
                PutObjectResponse resp = _client.PutObjectAsync(request).GetAwaiter().GetResult();
                return resp?.HttpStatusCode == HttpStatusCode.OK;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// 分片上传
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <param name="localFileName"></param>
        /// <returns></returns>
        public bool MultipartUpload(string bucketName, string objectName, string localFileName)
        {
            bool bRst = true;
            var initRequest = _client.InitiateMultipartUploadAsync(new InitiateMultipartUploadRequest() { BucketName = bucketName, Key = objectName }).GetAwaiter().GetResult();
            // 计算分片总数。
            long partSize = PartSize;
            var fi = new FileInfo(localFileName);
            var fileSize = fi.Length;
            var partCount = fileSize / partSize;
            if (partCount > MaxPart)
            {
                partSize = fileSize / MaxPart;
                partCount = MaxPart;
            }
            if (fileSize % partSize != 0)
            {
                partCount++;
            }

            var partETags = new List<PartETag>();
            try
            {
                using (var fs = File.Open(localFileName, FileMode.Open))
                {
                    for (var i = 0; i < partCount; i++)
                    {
                        var skipBytes = (long)partSize * i;
                        // 定位到本次上传的起始位置。
                        fs.Seek(skipBytes, 0);
                        // 计算本次上传的分片大小，最后一片为剩余的数据大小。
                        var size = (partSize < fileSize - skipBytes) ? partSize : (fileSize - skipBytes);
                        var request = new UploadPartRequest
                        {
                            BucketName = bucketName,
                            Key = objectName,
                            UploadId = initRequest.UploadId,
                            InputStream = fs,
                            PartSize = size,
                            PartNumber = i + 1
                        };
                        // 调用UploadPart接口执行上传功能，返回结果中包含了这个数据片的ETag值。
                        var result = _client.UploadPartAsync(request).GetAwaiter().GetResult();
                        partETags.Add(new PartETag { ETag = result.ETag, PartNumber = result.PartNumber });

                        //传递进度
                        Progress?.Invoke((int)(1.0F * (i + 1) / partCount * 100));
                        Console.WriteLine("finish {0}/{1}", partETags.Count, partCount);
                    }
                    Console.WriteLine("Put multi part upload succeeded");
                }
            }
            catch (Exception ex)
            {
                bRst = false;
                Console.WriteLine("Put multi part upload failed, {0}", ex.Message);
            }
            // 完成分片上传。
            try
            {
                var completeMultipartUploadRequest = new CompleteMultipartUploadRequest()
                {
                    BucketName = bucketName,
                    Key = objectName,
                    UploadId = initRequest.UploadId
                };
                foreach (var partETag in partETags)
                {
                    completeMultipartUploadRequest.PartETags.Add(partETag);
                }
                var result = _client.CompleteMultipartUploadAsync(completeMultipartUploadRequest).GetAwaiter().GetResult();
                Console.WriteLine("complete multi part succeeded");
            }
            catch (Exception ex)
            {
                bRst = false;
                Console.WriteLine("complete multi part failed, {0}", ex.Message);
            }
            return bRst;
        }

        /// <summary>
        /// 分片上传
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <param name="localFileName"></param>
        /// <param name="uploadId"></param>
        /// <returns></returns>
        public bool ResumableUploadObject(string bucketName, string objectName, string localFileName, ref string requestId)
        {
            bool bRst = true;
            var partETags = new List<PartETag>();
            ListPartsResponse parts = null;

            //未提供RequestId直接（分片）上传，否则断点续传
            if (string.IsNullOrWhiteSpace(requestId))
            {
                var initRequest = _client.InitiateMultipartUploadAsync(new InitiateMultipartUploadRequest() { BucketName = bucketName, Key = objectName }).GetAwaiter().GetResult();
                requestId = initRequest.UploadId;
            }
            else
                parts = _client.ListPartsAsync(new ListPartsRequest { BucketName = bucketName, Key = objectName, UploadId = requestId }).GetAwaiter().GetResult();

            // 计算分片总数。
            long partSize = PartSize;
            var fi = new FileInfo(localFileName);
            var fileSize = fi.Length;
            var partCount = fileSize / partSize;
            if (partCount > MaxPart)
            {
                partSize = fileSize / MaxPart;
                partCount = MaxPart;
            }
            if (fileSize % partSize != 0)
            {
                partCount++;
            }

            //追加已上传完成的分片
            if (parts != null && parts.Parts.Any())
                partETags.AddRange(parts.Parts);
            try
            {
                using (var fs = File.Open(localFileName, FileMode.Open))
                {
                    for (var i = 0; i < partCount; i++)
                    {
                        if (parts != null && i < parts?.Parts?.Last()?.PartNumber)
                            continue;
                        var skipBytes = (long)partSize * i;
                        // 定位到本次上传的起始位置。
                        fs.Seek(skipBytes, 0);
                        // 计算本次上传的分片大小，最后一片为剩余的数据大小。
                        var size = (partSize < fileSize - skipBytes) ? partSize : (fileSize - skipBytes);
                        var request = new UploadPartRequest
                        {
                            BucketName = bucketName,
                            Key = objectName,
                            UploadId = requestId,
                            InputStream = fs,
                            PartSize = size,
                            PartNumber = i + 1
                        };
                        // 调用UploadPart接口执行上传功能，返回结果中包含了这个数据片的ETag值。
                        var result = _client.UploadPartAsync(request).GetAwaiter().GetResult();
                        partETags.Add(new PartETag { ETag = result.ETag, PartNumber = result.PartNumber });

                        //传递进度
                        Progress?.Invoke((int)(1.0F * (i + 1) / partCount * 100));
                        Console.WriteLine("finish {0}/{1}", partETags.Count, partCount);
                    }
                    Console.WriteLine("Put multi part upload succeeded");
                }
            }
            catch (Exception ex)
            {
                bRst = false;
                Console.WriteLine($"requestId：{requestId}");
                Console.WriteLine("Put multi part upload failed, {0}", ex.Message);
                throw ex;
            }
            // 完成分片上传。
            try
            {
                var completeMultipartUploadRequest = new CompleteMultipartUploadRequest()
                {
                    BucketName = bucketName,
                    Key = objectName,
                    UploadId = requestId
                };

                foreach (var partETag in partETags)
                {
                    completeMultipartUploadRequest.PartETags.Add(partETag);
                }
                var result = _client.CompleteMultipartUploadAsync(completeMultipartUploadRequest).GetAwaiter().GetResult();
                Console.WriteLine("complete multi part succeeded");
            }
            catch (Exception ex)
            {
                bRst = false;
                Console.WriteLine("complete multi part failed, {0}", ex.Message);
                throw ex;
            }
            return bRst;
        }
    }
}
